Skip to content

feat(frontend): add team management, notification drawer, and plan updates#129

Merged
omsherikar merged 1 commit intomainfrom
feat/team-management-notifications-plan-updates
Mar 31, 2026
Merged

feat(frontend): add team management, notification drawer, and plan updates#129
omsherikar merged 1 commit intomainfrom
feat/team-management-notifications-plan-updates

Conversation

@omsherikar
Copy link
Copy Markdown
Collaborator

@omsherikar omsherikar commented Mar 31, 2026

  • Add TeamManagement.tsx: team dashboard with members, invites, activity, usage tabs, and per-member usage breakdown (lazy-loaded, cached)
  • Redesign notification system: full-height slide-in drawer with filter tabs (All/Unread/Team/Billing), date grouping, per-item mark-read and delete, load-more pagination, and type-specific icons
  • Add team API keys UI matching individual keys (revoke/delete, z-index fix)
  • Fix team invite duplicate check to exclude declined invites
  • Fix heatmap: replace invisible bar chart with cell-based contribution grid
  • Update plan feature descriptions: Pro gains LLM access features, Enterprise gains team management — across Billing, Onboarding, and PricingSection

Summary by CodeRabbit

New Features

  • Added team API keys management for team owners and administrators
  • Launched Team Management dashboard with member invitations, role management, and audit log tracking
  • Redesigned notifications system with full-screen drawer, filter tabs, and quick action buttons for invite acceptance/decline

Enhanced Features

  • Updated Pro plan feature highlights: LLM-powered analysis & autofix and AI refactoring with verification
  • Extended Enterprise plan to include team management & collaboration capabilities

…dates

- Add TeamManagement.tsx: team dashboard with members, invites, activity,
  usage tabs, and per-member usage breakdown (lazy-loaded, cached)
- Redesign notification system: full-height slide-in drawer with filter tabs
  (All/Unread/Team/Billing), date grouping, per-item mark-read and delete,
  load-more pagination, and type-specific icons
- Add team API keys UI matching individual keys (revoke/delete, z-index fix)
- Fix team invite duplicate check to exclude declined invites
- Fix heatmap: replace invisible bar chart with cell-based contribution grid
- Update plan feature descriptions: Pro gains LLM access features, Enterprise
  gains team management — across Billing, Onboarding, and PricingSection

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 31, 2026 10:52
@vercel
Copy link
Copy Markdown

vercel bot commented Mar 31, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
refactron Ready Ready Preview, Comment Mar 31, 2026 10:52am

@github-actions github-actions bot added type:refactor Code refactoring type:feature New feature labels Mar 31, 2026
@github-actions
Copy link
Copy Markdown

⚠️ Deprecation Warning: The deny-licenses option is deprecated for possible removal in the next major release. For more information, see issue 997.

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Snapshot Warnings

⚠️: No snapshots were found for the head SHA caefd20.
Ensure that dependencies are being submitted on PR branches and consider enabling retry-on-snapshot-warnings. See the documentation for more information and troubleshooting advice.

Scanned Files

None

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 31, 2026

📝 Walkthrough

Walkthrough

This PR introduces team management features for Enterprise users, including a new TeamManagement component, team-scoped API keys, invite/acceptance workflows, role-based access controls, and audit logging. It extends the auth context with team-related fields (teamRole, teamName, effectivePlan) and updates pricing displays to reflect new team collaboration capabilities.

Changes

Cohort / File(s) Summary
Team Management
src/components/TeamManagement.tsx
New component implementing team dashboard with Members, Invites, and Activity (audit log + 30-day heatmap) tabs; supports inviting/removing members, role changes, and displays usage analytics; gated to Enterprise plan users.
Team API Keys
src/components/ApiKeys.tsx, src/services/apiKey.service.ts
Extended ApiKeys component with team-scoped key creation/management UI for team owners/admins; added listTeamApiKeys() and createTeamApiKey() service methods; modified key refresh behavior to use background loading.
Auth & Routing
src/hooks/useAuth.tsx, src/components/AuthApp.tsx
Added optional teamRole, teamName, and effectivePlan fields to User interface; registered protected /settings/team route for TeamManagement component.
Notifications & Invites
src/components/DashboardLayout.tsx
Replaced dropdown notifications UI with full-screen drawer featuring filter tabs, time-based grouping, and pagination; added invite accept/decline handlers with per-invite action state and error handling; extended Notification model with invite token metadata.
Usage & Plan Logic
src/components/Usage.tsx
Updated plan derivation to prioritize effectivePlan over user.plan for access control and quota determination.
Pricing/Marketing Content
src/components/Billing.tsx, src/components/Onboarding.tsx, src/components/PricingSection.tsx
Updated feature lists across subscription tiers: Pro now includes "LLM-powered analysis & autofix" and "AI refactoring with verification"; Enterprise adds "Team management & collaboration".

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant DashboardLayout as Dashboard UI
    participant AuthContext
    participant API as Backend API
    participant AuthService

    User->>DashboardLayout: Receive notification with invite token
    DashboardLayout->>API: GET /api/notifications (paginated)
    API-->>DashboardLayout: Return notifications + invite metadata
    DashboardLayout->>User: Display drawer with invite notification

    User->>DashboardLayout: Click "Accept Invite"
    DashboardLayout->>API: POST /api/team/invites/accept
    API-->>DashboardLayout: Invite accepted, team assigned
    DashboardLayout->>AuthService: Request auth refresh (updateUser)
    AuthService->>API: GET /api/auth/me
    API-->>AuthService: Return updated user with teamRole, teamName
    AuthService->>AuthContext: Update user context
    AuthContext-->>DashboardLayout: User now has team access
    DashboardLayout->>User: Reflect team affiliation in nav/sidebar
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

type:feature, type:refactor

Poem

🐰 A team now springs from Enterprise dreams,
With invites sent and roles in streams,
Keys scoped to squads, activity tracked,
Dashboards bright, permissions stacked!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the three main changes: team management (TeamManagement.tsx), notification drawer (redesigned notifications UI), and plan updates (feature descriptions).

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/team-management-notifications-plan-updates

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown

👋 Thanks for opening this pull request! A maintainer will review it soon. Please make sure all CI checks pass.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an Enterprise-oriented team dashboard and expands the UI to support team-scoped features (team API keys, team nav) while modernizing notifications into a full-height drawer and updating plan messaging across the app.

Changes:

  • Added TeamManagement page with members/invites/activity tabs, invite-accept flow, and team usage breakdown with lazy-loaded per-member details.
  • Redesigned notifications into a right-side drawer with filters, grouping, pagination, and per-item actions (mark read/delete) plus invite accept/decline actions.
  • Updated plan feature copy (Pro/Enterprise) and wired effectivePlan/teamRole into gating logic for usage/API keys/nav.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/services/apiKey.service.ts Adds team-scoped key list/create service calls and expands key response shape.
src/hooks/useAuth.tsx Extends User shape to include teamRole, teamName, effectivePlan.
src/components/Usage.tsx Uses effectivePlan for plan gating/quota selection.
src/components/TeamManagement.tsx New team dashboard UI with members/invites/activity + usage details + invite acceptance.
src/components/PricingSection.tsx Updates Pro/Enterprise feature descriptions.
src/components/Onboarding.tsx Updates plan feature descriptions shown during onboarding.
src/components/DashboardLayout.tsx Adds team nav entry and replaces popover notifications with a drawer + actions.
src/components/Billing.tsx Updates plan feature descriptions on billing page.
src/components/AuthApp.tsx Adds protected route for /settings/team.
src/components/ApiKeys.tsx Adds team API keys section + create modal and uses effectivePlan for gating.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +301 to +315
const loadDashboard = useCallback(async () => {
setLoading(true);
setError(null);
try {
// Enterprise owners: ensure team exists on first load only
if (isEnterprise && !teamData) {
const createRes = await fetch(`${apiBase}/api/team`, {
method: 'POST',
headers,
});
if (!createRes.ok) {
setError('Failed to initialize your team. Please try again.');
return;
}
}
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

loadDashboard reads teamData but the useCallback dependency list omits it. Because of the stale closure, if (isEnterprise && !teamData) will keep seeing the initial teamData value and can repeatedly POST /api/team on subsequent loadDashboard() calls (e.g., retry button or after invite acceptance). Include teamData in the dependency array or replace this check with a ref/flag that tracks one-time initialization so the create call only happens once per session.

Copilot uses AI. Check for mistakes.
null
);

const headers = { Authorization: `Bearer ${token}` };
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

headers is built from localStorage.getItem('accessToken') without guarding for null, which can result in sending Authorization: Bearer null to several endpoints. Build headers conditionally (only include Authorization when a token is present) or reuse the getAuthToken pattern used in the API service layer to avoid confusing auth failures.

Suggested change
const headers = { Authorization: `Bearer ${token}` };
const headers: HeadersInit = token
? { Authorization: `Bearer ${token}` }
: {};

Copilot uses AI. Check for mistakes.
Comment on lines +277 to +283
setNotifications(data.notifications ?? []);
}
setNotifTotal(data.total ?? 0);
}
} catch {}
},
[apiBase, token]
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

notifPage can get out of sync with notifications: the periodic fetchNotifications() refresh replaces the list with page 1 but doesn't reset notifPage back to 1. After a refresh, clicking “Load more” will skip a page (e.g., from page 2 → fetch page 3). When append is false (or when opening the drawer), reset notifPage to 1 (and optionally clear any existing items) to keep pagination consistent.

Suggested change
setNotifications(data.notifications ?? []);
}
setNotifTotal(data.total ?? 0);
}
} catch {}
},
[apiBase, token]
// Reset pagination when replacing the list with page 1
setNotifPage(1);
setNotifications(data.notifications ?? []);
}
setNotifTotal(data.total ?? 0);
}
} catch {}
},
[apiBase, token, setNotifPage]

Copilot uses AI. Check for mistakes.
{/* New Key Display Modal */}
{newlyCreatedKey && (
<div className="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-50 p-4">
<div className="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-60 p-4">
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tailwind doesn't include a z-60 utility by default, and tailwind.config.js doesn't extend zIndex, so z-60 will be a no-op and the modal may still sit behind other overlays. Use an arbitrary value (e.g., z-[60]) or extend the Tailwind theme zIndex scale to include 60.

Suggested change
<div className="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-60 p-4">
<div className="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-[60] p-4">

Copilot uses AI. Check for mistakes.
Comment on lines +1079 to +1085
const data = await res.json();
if (data.success) {
setMemberUsageCache(prev => ({
...prev,
[m.userId]: data.usage,
}));
}
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The per-member usage fetch in the member tab (onClick for a member button) has no catch and doesn't check res.ok. If the request fails or res.json() throws, it will surface as an unhandled promise rejection and the UI will misleadingly fall back to “No usage data for this period.” Add proper error handling (check res.ok, handle data.success === false, and store an error state for this view).

Suggested change
const data = await res.json();
if (data.success) {
setMemberUsageCache(prev => ({
...prev,
[m.userId]: data.usage,
}));
}
if (!res.ok) {
throw new Error(`Failed to load member usage: ${res.status} ${res.statusText}`);
}
const data = await res.json();
if (!data?.success) {
throw new Error(data?.message || 'Failed to load member usage');
}
setMemberUsageCache(prev => ({
...prev,
[m.userId]: data.usage,
}));
} catch (error) {
console.error('Error fetching member usage', error);
// Store an explicit error state for this member in the cache
setMemberUsageCache(prev => ({
...prev,
[m.userId]: null,
}));

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (3)
src/components/DashboardLayout.tsx (1)

366-404: Optimistic updates without rollback on failure.

The handleMarkRead, handleDeleteNotification, and handleClearAll handlers perform optimistic UI updates but silently swallow API errors without rolling back the local state. While this is acceptable for non-critical actions like mark-read, users may be confused if deleted notifications reappear on page refresh.

Consider adding a toast notification on failure, or rolling back the optimistic update:

💡 Example with rollback for delete
   const handleDeleteNotification = useCallback(
     async (notifId: string) => {
+      const prev = notifications;
       setNotifications(prev => prev.filter(n => n.id !== notifId));
       setNotifTotal(prev => Math.max(0, prev - 1));
       try {
         await fetch(`${apiBase}/api/notifications/${notifId}`, {
           method: 'DELETE',
           headers: { Authorization: `Bearer ${token}` },
         });
-      } catch {}
+      } catch {
+        // Rollback on failure
+        setNotifications(prev);
+        setNotifTotal(t => t + 1);
+      }
     },
-    [apiBase, token]
+    [apiBase, token, notifications]
   );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/DashboardLayout.tsx` around lines 366 - 404, The optimistic
updates in handleMarkRead, handleDeleteNotification, and handleClearAll
immediately mutate local state but swallow fetch errors, so failures aren’t
surfaced and state isn’t rolled back; update each handler (handleMarkRead,
handleDeleteNotification, handleClearAll) to capture the previous state before
mutating, attempt the API call, and on catch restore the prior state and surface
an error (e.g., call a toast/error handler or set an error state) so the UI
reflects the true server state; ensure you only clear the saved previous state
after a successful response to avoid memory leaks.
src/components/TeamManagement.tsx (2)

411-461: Invite acceptance from URL lacks duplicate-submission protection.

If a user refreshes the page while the invite is still in the URL (?invite=<token>), the acceptance flow will be triggered again. While the backend likely rejects already-accepted tokens, the UX could be confusing (showing "Accepting invitation…" followed by an error).

Consider clearing the URL parameter after initiating the request, or checking inviteResult state before re-triggering:

💡 Suggested improvement
   useEffect(() => {
     if (!inviteToken) return;
+    // Clear the invite param from URL to prevent re-triggering on refresh
+    const url = new URL(window.location.href);
+    url.searchParams.delete('invite');
+    window.history.replaceState({}, '', url.toString());
+
     const capturedToken = inviteToken;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/TeamManagement.tsx` around lines 411 - 461, The invite
auto-accept effect can re-trigger on page refresh; fix by removing the invite
query param immediately after capturing it and also short-circuiting if an
invite result already exists: inside the useEffect that reads inviteToken (the
effect containing capturedToken, setInviteAccepting, setInviteResult,
loadDashboard, updateUser), after const capturedToken = inviteToken call remove
the invite param via const url = new URL(window.location.href);
url.searchParams.delete('invite'); window.history.replaceState({}, '',
url.toString()); and at the top of the effect return early if inviteResult is
set (e.g. if (inviteResult) return;) to prevent duplicate submissions.

1067-1089: Per-member usage fetch lacks loading state feedback and race condition handling.

When clicking a member tab, the fetch is initiated inline within the onClick. If the user clicks multiple member tabs rapidly, multiple concurrent fetches can occur, and memberUsageLoading may be set to false by an earlier request completing after a later one starts, causing incorrect loading state display.

Consider debouncing or tracking which member is currently loading:

💡 Suggested improvement
+  const [loadingMemberUsageId, setLoadingMemberUsageId] = useState<string | null>(null);

   // In the onClick handler:
   onClick={async () => {
     setUsageTab(m.userId);
     if (memberUsageCache[m.userId]) return;
-    setMemberUsageLoading(true);
+    setLoadingMemberUsageId(m.userId);
     try {
       // ... fetch
     } finally {
-      setMemberUsageLoading(false);
+      setLoadingMemberUsageId(null);
     }
   }}

   // In the loading check:
-  {memberUsageLoading ? (
+  {loadingMemberUsageId === usageTab ? (
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/TeamManagement.tsx` around lines 1067 - 1089, The onClick
fetch starts inline and uses a single boolean memberUsageLoading which races
when clicks happen rapidly; change to track loading per-member (e.g., introduce
currentMemberLoadingId or a memberLoading map) and only clear loading or write
results if the response matches that tracked id. Specifically, in the onClick
handler around setUsageTab and setMemberUsageLoading, set a
currentMemberLoadingId (or set memberLoading[m.userId]=true), include that id
with the fetch (or use an AbortController), and when the fetch resolves/rejects
only call setMemberUsageCache and setMemberUsageLoading(false) if the stored
currentMemberLoadingId equals m.userId (or update the map for that user),
ensuring stale responses don't flip the global loading state; reference
setUsageTab, memberUsageCache, setMemberUsageCache, setMemberUsageLoading, and
m.userId to locate and update the code.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/ApiKeys.tsx`:
- Line 684: The Tailwind z-index class used in the ApiKeys component's modal
wrapper is invalid: replace the non-existent "z-60" in the div with className
"fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center
z-60 p-4" with the arbitrary-value syntax "z-[60]" so Tailwind generates the
CSS; locate the div inside the ApiKeys component (the modal overlay) and update
the className accordingly.

In `@src/components/DashboardLayout.tsx`:
- Around line 1086-1098: The "Load more" button is currently rendered inside the
groupedNotifs.map() loop causing it to appear after each date group; move the
entire conditional block that references notifications, notifTotal,
notifLoadingMore, and handleLoadMore out of the groupedNotifs.map() rendering so
it renders once after the map completes (e.g., place the conditional after the
mapped JSX that uses groupedNotifs.map). Ensure the button still uses the same
props/handlers (notifLoadingMore, handleLoadMore) and the same conditional check
(notifications.length < notifTotal) so behavior is unchanged.

In `@src/components/TeamManagement.tsx`:
- Around line 1086-1098: The "Load more" button is being rendered inside the
groupedNotifs.map loop (see usage of groupedNotifs.map and the JSX that renders
date groups), causing it to appear for each date group; move the Load more
button out of that loop so it is rendered once after the mapped groups (i.e.,
place the button JSX after the groupedNotifs.map(...) return block), keeping its
existing props/state references (e.g., isLoading, onClick handler, classes) and
ensuring any surrounding container/layout (the element that wraps the groups)
still encloses the button for proper styling and alignment.

---

Nitpick comments:
In `@src/components/DashboardLayout.tsx`:
- Around line 366-404: The optimistic updates in handleMarkRead,
handleDeleteNotification, and handleClearAll immediately mutate local state but
swallow fetch errors, so failures aren’t surfaced and state isn’t rolled back;
update each handler (handleMarkRead, handleDeleteNotification, handleClearAll)
to capture the previous state before mutating, attempt the API call, and on
catch restore the prior state and surface an error (e.g., call a toast/error
handler or set an error state) so the UI reflects the true server state; ensure
you only clear the saved previous state after a successful response to avoid
memory leaks.

In `@src/components/TeamManagement.tsx`:
- Around line 411-461: The invite auto-accept effect can re-trigger on page
refresh; fix by removing the invite query param immediately after capturing it
and also short-circuiting if an invite result already exists: inside the
useEffect that reads inviteToken (the effect containing capturedToken,
setInviteAccepting, setInviteResult, loadDashboard, updateUser), after const
capturedToken = inviteToken call remove the invite param via const url = new
URL(window.location.href); url.searchParams.delete('invite');
window.history.replaceState({}, '', url.toString()); and at the top of the
effect return early if inviteResult is set (e.g. if (inviteResult) return;) to
prevent duplicate submissions.
- Around line 1067-1089: The onClick fetch starts inline and uses a single
boolean memberUsageLoading which races when clicks happen rapidly; change to
track loading per-member (e.g., introduce currentMemberLoadingId or a
memberLoading map) and only clear loading or write results if the response
matches that tracked id. Specifically, in the onClick handler around setUsageTab
and setMemberUsageLoading, set a currentMemberLoadingId (or set
memberLoading[m.userId]=true), include that id with the fetch (or use an
AbortController), and when the fetch resolves/rejects only call
setMemberUsageCache and setMemberUsageLoading(false) if the stored
currentMemberLoadingId equals m.userId (or update the map for that user),
ensuring stale responses don't flip the global loading state; reference
setUsageTab, memberUsageCache, setMemberUsageCache, setMemberUsageLoading, and
m.userId to locate and update the code.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8c6de9f2-a36c-4d7a-99f1-d210fda9d23e

📥 Commits

Reviewing files that changed from the base of the PR and between 55e6775 and caefd20.

📒 Files selected for processing (10)
  • src/components/ApiKeys.tsx
  • src/components/AuthApp.tsx
  • src/components/Billing.tsx
  • src/components/DashboardLayout.tsx
  • src/components/Onboarding.tsx
  • src/components/PricingSection.tsx
  • src/components/TeamManagement.tsx
  • src/components/Usage.tsx
  • src/hooks/useAuth.tsx
  • src/services/apiKey.service.ts

{/* New Key Display Modal */}
{newlyCreatedKey && (
<div className="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-50 p-4">
<div className="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-60 p-4">
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if z-60 is configured in Tailwind config
fd -t f "tailwind.config" --exec cat {} | head -100

Repository: Refactron-ai/Refactron_Website

Length of output: 3018


🏁 Script executed:

# Verify z-60 is used in ApiKeys.tsx at the specified line
rg "z-60" src/components/ApiKeys.tsx -n

# Get the complete tailwind.config.js file
find . -name "tailwind.config.js" -o -name "tailwind.config.ts" | head -1 | xargs cat

Repository: Refactron-ai/Refactron_Website

Length of output: 3343


Fix z-60 to z-[60] - not a valid Tailwind class.

Tailwind CSS's default z-index scale only includes values up to z-50. The class z-60 is not configured in this project's tailwind.config.js, so it will not generate any CSS. Use the arbitrary value syntax instead.

Fix
-        <div className="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-60 p-4">
+        <div className="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-[60] p-4">
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div className="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-60 p-4">
<div className="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-[60] p-4">
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ApiKeys.tsx` at line 684, The Tailwind z-index class used in
the ApiKeys component's modal wrapper is invalid: replace the non-existent
"z-60" in the div with className "fixed inset-0 bg-black/50 backdrop-blur-sm
flex items-center justify-center z-60 p-4" with the arbitrary-value syntax
"z-[60]" so Tailwind generates the CSS; locate the div inside the ApiKeys
component (the modal overlay) and update the className accordingly.

Comment on lines +1086 to +1098
{notifications.length < notifTotal && (
<div className="px-5 py-4 text-center border-t border-white/[0.04]">
<button
onClick={handleLoadMore}
disabled={notifLoadingMore}
className="text-xs text-neutral-600 hover:text-white transition-colors disabled:opacity-40"
>
{notifLoadingMore
? 'Loading…'
: `Load more (${notifTotal - notifications.length} remaining)`}
</button>
</div>
)}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

"Load more" button renders inside each date group.

Same issue as in TeamManagement.tsx — the "Load more" button is inside the groupedNotifs.map() loop, so it could appear after each date group instead of once at the bottom.

🐛 Proposed fix — move outside the loop
                         {groupedNotifs.map(group => (
                           <div key={group.label}>
                             {/* ... group content ... */}
-
-                            {/* Load more */}
-                            {notifications.length < notifTotal && (
-                              <div className="px-5 py-4 text-center border-t border-white/[0.04]">
-                                <button
-                                  onClick={handleLoadMore}
-                                  disabled={notifLoadingMore}
-                                  className="text-xs text-neutral-600 hover:text-white transition-colors disabled:opacity-40"
-                                >
-                                  {notifLoadingMore
-                                    ? 'Loading…'
-                                    : `Load more (${notifTotal - notifications.length} remaining)`}
-                                </button>
-                              </div>
-                            )}
                           </div>
                         ))}
+
+                        {/* Load more */}
+                        {notifications.length < notifTotal && (
+                          <div className="px-5 py-4 text-center border-t border-white/[0.04]">
+                            <button
+                              onClick={handleLoadMore}
+                              disabled={notifLoadingMore}
+                              className="text-xs text-neutral-600 hover:text-white transition-colors disabled:opacity-40"
+                            >
+                              {notifLoadingMore
+                                ? 'Loading…'
+                                : `Load more (${notifTotal - notifications.length} remaining)`}
+                            </button>
+                          </div>
+                        )}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/DashboardLayout.tsx` around lines 1086 - 1098, The "Load more"
button is currently rendered inside the groupedNotifs.map() loop causing it to
appear after each date group; move the entire conditional block that references
notifications, notifTotal, notifLoadingMore, and handleLoadMore out of the
groupedNotifs.map() rendering so it renders once after the map completes (e.g.,
place the conditional after the mapped JSX that uses groupedNotifs.map). Ensure
the button still uses the same props/handlers (notifLoadingMore, handleLoadMore)
and the same conditional check (notifications.length < notifTotal) so behavior
is unchanged.

Comment on lines +1086 to +1098
} finally {
setMemberUsageLoading(false);
}
}}
className={`text-xs px-3 py-1.5 rounded-lg border transition-colors ${
usageTab === m.userId
? 'border-white/[0.12] bg-white/[0.06] text-neutral-200'
: 'border-transparent text-neutral-500 hover:text-neutral-300'
}`}
>
{m.fullName ?? m.email.split('@')[0]}
</button>
))}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

"Load more" button is rendered inside each date group instead of once at the end.

The "Load more" button at lines 1086-1098 is rendered inside the groupedNotifs.map() loop, meaning it could appear after each date group (Today, Yesterday, Earlier) rather than once at the bottom of the list. This appears to be a placement error.

🐛 Proposed fix — move Load more outside the date group loop
                         {groupedNotifs.map(group => (
                           <div key={group.label}>
                             <p className="sticky top-0 px-5 py-2 text-[10px] font-semibold uppercase tracking-widest text-neutral-700 bg-[`#0d0d0d`]">
                               {group.label}
                             </p>
                             {group.items.map(n => {
                               // ... notification rendering
                             })}
-
-                            {/* Load more */}
-                            {notifications.length < notifTotal && (
-                              <div className="px-5 py-4 text-center border-t border-white/[0.04]">
-                                <button
-                                  onClick={handleLoadMore}
-                                  disabled={notifLoadingMore}
-                                  className="text-xs text-neutral-600 hover:text-white transition-colors disabled:opacity-40"
-                                >
-                                  {notifLoadingMore
-                                    ? 'Loading…'
-                                    : `Load more (${notifTotal - notifications.length} remaining)`}
-                                </button>
-                              </div>
-                            )}
                           </div>
                         ))}
+
+                        {/* Load more */}
+                        {notifications.length < notifTotal && (
+                          <div className="px-5 py-4 text-center border-t border-white/[0.04]">
+                            <button
+                              onClick={handleLoadMore}
+                              disabled={notifLoadingMore}
+                              className="text-xs text-neutral-600 hover:text-white transition-colors disabled:opacity-40"
+                            >
+                              {notifLoadingMore
+                                ? 'Loading…'
+                                : `Load more (${notifTotal - notifications.length} remaining)`}
+                            </button>
+                          </div>
+                        )}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/TeamManagement.tsx` around lines 1086 - 1098, The "Load more"
button is being rendered inside the groupedNotifs.map loop (see usage of
groupedNotifs.map and the JSX that renders date groups), causing it to appear
for each date group; move the Load more button out of that loop so it is
rendered once after the mapped groups (i.e., place the button JSX after the
groupedNotifs.map(...) return block), keeping its existing props/state
references (e.g., isLoading, onClick handler, classes) and ensuring any
surrounding container/layout (the element that wraps the groups) still encloses
the button for proper styling and alignment.

@omsherikar omsherikar merged commit b0ef5e0 into main Mar 31, 2026
18 of 19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type:feature New feature type:refactor Code refactoring

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants